﻿#region Copyright (c) 2012-10, Olaf Kober <amarok.projects@gmail.com>
//================================================================================
//	Permission is hereby granted, free of charge, to any person obtaining a copy
//	of this software and associated documentation files (the "Software"), to deal
//	in the Software without restriction, including without limitation the rights
//	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//	copies of the Software, and to permit persons to whom the Software is
//	furnished to do so, subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in
//	all copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//	THE SOFTWARE.
//================================================================================
#endregion

using System;


namespace Amarok
{
	/// <summary>
	/// This type represents a segment of bytes in an underlying byte array.
	/// </summary>
	public struct BufferSegment :
		IEquatable<BufferSegment>,
		ICloneable
	{
		#region ++ Public Static Interface ++

		/// <summary>
		/// Gets a buffer segment pointing to an empty buffer with zero-length.
		/// </summary>
		public static readonly BufferSegment Empty = new BufferSegment(Helper.EmptyBytes, 0, 0);

		#endregion

		#region ++ Public Interface ++

		/// <summary>
		/// A reference to the underlying buffer.
		/// </summary>
		public readonly Byte[] Buffer;

		/// <summary>
		/// The offset where the segment starts in the underlying buffer.
		/// </summary>
		public readonly Int32 Offset;

		/// <summary>
		/// The length of the segment in the underlying buffer.
		/// </summary>
		public readonly Int32 Count;


		/// <summary>
		/// Gets the byte value in the buffer segment on the specified offset.
		/// </summary>
		/// 
		/// <param name="offset">
		/// The offset in the buffer segment.</param>
		/// 
		/// <returns>
		/// The byte value in the buffer segment on the specified offset.</returns>
		/// 
		/// <exception cref="ArgumentOutOfRangeException">
		/// A value was passed to a method that did not accept it as a valid argument, because the value must be 
		/// equal to or greater than the minimum value and equal to or less than the maximum value of.</exception>
		public Byte this[Int32 offset]
		{
			get
			{
				Verify.InRange(offset, 0, this.Count - 1, "offset");
				return this.Buffer[this.Offset + offset];
			}
		}


		/// <summary>
		/// Initializes a new instance.
		/// </summary>
		/// 
		/// <param name="buffer">
		/// A reference to the underlying buffer.</param>
		public BufferSegment(Byte[] buffer)
			: this(buffer, 0, buffer.Length)
		{
		}

		/// <summary>
		/// Initializes a new instance.
		/// </summary>
		/// 
		/// <param name="buffer">
		/// A reference to the underlying buffer.</param>
		/// <param name="offset">
		/// The offset in the underlying buffer where the segment starts.</param>
		/// <param name="count">
		/// The length of the segment.</param>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// A value was passed to a method that did not accept it as a valid argument, because the value 
		/// must be positive (equal to or greater than zero).</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// An offset value was passed to a method that did not accept it as a valid argument, because the 
		/// offset exceeds the length of the associated byte array.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// A count value was passed to a method that did not accept it as a valid argument, because the count 
		/// exceeds the length of the associated byte array.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// Offset and count values were passed to a method that did not accept it as valid arguments, because 
		/// the sum of offset and count exceeds the length of the associated byte array.</exception>
		public BufferSegment(Byte[] buffer, Int32 offset, Int32 count)
		{
			Verify.BufferSegment(buffer, offset, count);

			this.Buffer = buffer;
			this.Offset = offset;
			this.Count = count;
		}


		/// <summary>
		/// Returns a string that represents the current  instance.
		/// </summary>
		/// 
		/// <returns>
		/// A string that represents the current  instance.</returns>
		public override String ToString()
		{
			return Helper.FormatBuffer(this.Buffer, this.Offset, this.Count);
		}

		/// <summary>
		/// Appends the supplied buffer segment to this one and returns a new buffer segment containing the result.
		/// </summary>
		/// 
		/// <param name="data">
		/// The buffer segment to append.</param>
		/// 
		/// <returns>
		/// A new buffer segment containing the result.</returns>
		public BufferSegment Add(BufferSegment data)
		{
			if (this.Count == 0)
				return data;
			if (data.Count == 0)
				return this;

			var length = this.Count + data.Count;
			var buffer = new Byte[length];

			System.Buffer.BlockCopy(this.Buffer, this.Offset, buffer, 0, this.Count);
			System.Buffer.BlockCopy(data.Buffer, data.Offset, buffer, this.Count, data.Count);
			return new BufferSegment(buffer, 0, length);
		}

		/// <summary>
		/// Discards the specified area from this buffer segment and returns the result as new buffer segment.
		/// </summary>
		/// 
		/// <param name="offset">
		/// The offset in the buffer segment where the area starts.</param>
		/// <param name="count">
		/// The count of bytes to discard.</param>
		/// 
		/// <returns>
		/// A new buffer segment containing the result. This returned buffer segment still refers the same 
		/// underlying byte array.</returns>
		/// 
		/// <exception cref="ArgumentOutOfRangeException">
		/// A value was passed to a method that did not accept it as a valid argument, because the value 
		/// must be positive (equal to or greater than zero).</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// An offset value was passed to a method that did not accept it as a valid argument, because the 
		/// offset exceeds the length of the associated byte array.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// A count value was passed to a method that did not accept it as a valid argument, because the count 
		/// exceeds the length of the associated byte array.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// Offset and count values were passed to a method that did not accept it as valid arguments, because 
		/// the sum of offset and count exceeds the length of the associated byte array.</exception>
		public BufferSegment Discard(Int32 offset, Int32 count)
		{
			Verify.BufferSegment(this, offset, count);

			// discard nothing
			if (count == 0)
				return this;

			// discard all
			if (count == this.Count)
				return BufferSegment.Empty;

			// discard from begin
			if (offset == 0)
				return new BufferSegment(this.Buffer, this.Offset + count, this.Count - count);

			// discard from end
			if (offset + count == this.Count)
				return new BufferSegment(this.Buffer, this.Offset, this.Count - count);

			if (offset < this.Count - offset - count)
			{
				// discard near begin -> copy bytes at begin to end
				System.Buffer.BlockCopy(
					this.Buffer, this.Offset,
					this.Buffer, this.Offset + offset + count - 1, offset);

				return new BufferSegment(this.Buffer, this.Offset + offset + count - 1, this.Count - count);
			}
			else
			{
				// discard near end -> copy bytes at end to begin
				System.Buffer.BlockCopy(
					this.Buffer, this.Offset + offset + count,
					this.Buffer, this.Offset + offset, this.Count - offset - count);

				return new BufferSegment(this.Buffer, this.Offset, this.Count - count);
			}
		}

		/// <summary>
		/// Creates a new object that is a copy of the current instance. This method performs a deep clone, 
		/// meaning a new underlying byte array is allocated for the returned buffer segment.
		/// </summary>
		/// 
		/// <returns>
		/// A new object that is a copy of this instance.</returns>
		public BufferSegment Clone()
		{
			if (this.Count == 0)
				return BufferSegment.Empty;
			else
				return new BufferSegment(this.ToArray());
		}

		/// <summary>
		/// Creates a new object that is a copy of the current instance. This method performs a deep clone, 
		/// meaning a new underlying byte array is allocated for the returned buffer segment.
		/// </summary>
		/// 
		/// <returns>
		/// A new object that is a copy of this instance.</returns>
		Object ICloneable.Clone()
		{
			return this.Clone();
		}

		/// <summary>
		/// Returns a new byte array containing the buffer segment.
		/// </summary>
		/// 
		/// <returns>
		/// A newly create byte array.</returns>
		public Byte[] ToArray()
		{
			return this.ToArray(0, this.Count);
		}

		/// <summary>
		/// Returns a new byte array containing the buffer segment.
		/// </summary>
		/// 
		/// <param name="offset">
		/// The offset in the buffer segment where the area starts.</param>
		/// <param name="count">
		/// The count of bytes to discard.</param>
		/// 
		/// <returns>
		/// A newly create byte array.</returns>
		/// 
		/// <exception cref="ArgumentOutOfRangeException">
		/// A value was passed to a method that did not accept it as a valid argument, because the value 
		/// must be positive (equal to or greater than zero).</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// An offset value was passed to a method that did not accept it as a valid argument, because the 
		/// offset exceeds the length of the associated byte array.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// A count value was passed to a method that did not accept it as a valid argument, because the count 
		/// exceeds the length of the associated byte array.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// Offset and count values were passed to a method that did not accept it as valid arguments, because 
		/// the sum of offset and count exceeds the length of the associated byte array.</exception>
		public Byte[] ToArray(Int32 offset, Int32 count)
		{
			Verify.BufferSegment(this, offset, count);

			if (this.Count == 0)
			{
				return Helper.EmptyBytes;
			}
			else
			{
				var buffer = new Byte[count];
				System.Buffer.BlockCopy(this.Buffer, this.Offset + offset, buffer, 0, count);
				return buffer;
			}
		}

		#endregion

		#region ++ Public Interface (Overloaded Operators) ++

		/// <summary>
		/// Adds the supplied buffer segments to one.
		/// </summary>
		/// 
		/// <param name="a">
		/// The first buffer segment.</param>
		/// <param name="b">
		/// The second buffer segment.</param>
		/// 
		/// <returns>
		/// The resulting buffer segment.</returns>
		public static BufferSegment operator +(BufferSegment a, BufferSegment b)
		{
			return a.Add(b);
		}

		#endregion

		#region ++ Public Interface (GetHashCode, Equals) ++

		/// <summary>
		/// Returns the hash code for the current instance. 
		/// </summary>
		/// 
		/// <returns>
		/// A 32-bit signed integer hash code.</returns>
		public override Int32 GetHashCode()
		{
			return Buffer.GetHashCode() ^ (Offset << 16) ^ (Count << 8);
		}

		/// <summary>
		/// Determines whether the specified Object is equal to the current Object.
		/// </summary>
		/// 
		/// <param name="other">
		/// The Object to compare with the current Object.</param>
		/// 
		/// <returns>
		/// true if the specified Object is equal to the current Object; otherwise, false.</returns>
		public override Boolean Equals(Object other)
		{
			// null reference?
			if (Object.ReferenceEquals(other, null))
				return false;

			// same type?
			if (this.GetType() != other.GetType())
				return false;

			return this.Equals((BufferSegment)other);
		}

		/// <summary>
		/// Determines whether the specified BufferSegment is equal to the current BufferSegment.
		/// </summary>
		/// 
		/// <param name="other">
		/// The BufferSegment to compare with the current BufferSegment.</param>
		/// 
		/// <returns>
		/// true if the specified BufferSegment is equal to the current BufferSegment; 
		/// otherwise, false.</returns>
		public Boolean Equals(BufferSegment other)
		{
			// compare via fields
			return this.Buffer == other.Buffer &&
				this.Offset == other.Offset &&
				this.Count == other.Count;
		}

		/// <summary>
		/// Determines whether the specified BufferSegment are equal.
		/// </summary>
		/// 
		/// <param name="a">
		/// The first BufferSegment to compare.</param>
		/// <param name="b">
		/// The second BufferSegment to compare.</param>
		/// <returns>
		/// true if the specified BufferSegment are equal; otherwise, false.</returns>
		public static Boolean operator ==(BufferSegment a, BufferSegment b)
		{
			return a.Equals(b);
		}

		/// <summary>
		/// Determines whether the specified BufferSegment are unequal.
		/// </summary>
		/// 
		/// <param name="a">
		/// The first BufferSegment to compare.</param>
		/// <param name="b">
		/// The second BufferSegment to compare.</param>
		/// <returns>
		/// true if the specified BufferSegment are unequal; otherwise, false.</returns>
		public static Boolean operator !=(BufferSegment a, BufferSegment b)
		{
			return !a.Equals(b);
		}

		#endregion

	}
}
